﻿using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using GameControler.Game;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;

namespace GameControler.Emgu
{

    public enum MatchColorSpaceType
    {
        Gray,
        RGB,
        HSV
    }

    /// <summary>
    /// TM_TYPE.CV_TM_SQDIFF_NORMED 取最小值为匹配相似度
    /// </summary>
    public class MatchData
    {
        public bool IsMatch { get; set; }
        public double MatchRate { get; set; }
        public Point Point { get; set; }
        public TemplateView TemplateView { get; set; }
    }

    public static class JilyEmgu
    {
        public static int TopBorrder = 10;

        public static int ErrorSapn = 3;

        public static MatchData MatchResult(this Bitmap intput, TemplateView template, double MatchRate = 0.3)
        {
            var md = new MatchData() { TemplateView = template };
            var intputiamge = new Image<Bgr, byte>(intput);
            var templateimage = new Image<Bgr, byte>(template.Image);

            var intputiamgeScale = intputiamge.Clone();
            var templateimageScale = templateimage.Clone();

            if (MatchRate != 1d)
            {
                intputiamgeScale = intputiamgeScale.Resize(MatchRate, INTER.CV_INTER_LINEAR);
                templateimageScale = templateimageScale.Resize(MatchRate, INTER.CV_INTER_LINEAR);
            }

            double mpatchrategray, mpatchratehsv, mpatchratergb = 0;
            var firstpoint = GetBestPoint(intputiamgeScale.Convert<Gray, Byte>(), templateimageScale.Convert<Gray, Byte>(), out  mpatchrategray, MatchColorSpaceType.Gray);
            var second = GetBestPoint(intputiamgeScale.Convert<Hsv, Byte>(), templateimageScale.Convert<Hsv, Byte>(), out  mpatchratehsv, MatchColorSpaceType.HSV);
            var third = GetBestPoint(intputiamgeScale.Copy(), templateimageScale.Copy(), out mpatchratergb, MatchColorSpaceType.RGB);

            if (firstpoint.IsIngornSpanSame(second) && second.IsIngornSpanSame(third))
            {
                var y = (int)(firstpoint.Y / MatchRate);
                if (TopBorrder < y)
                {
                    md.MatchRate = (new List<double>() { mpatchrategray, mpatchratehsv, mpatchratergb }).Min() * 100000000;
                    md.IsMatch = true;
                    md.Point = new Point((int)(firstpoint.X / MatchRate) + template.Point.X, y + template.Point.Y);
                }
                //var s = 1d;
                //var p = GetBestPoint(intputiamge.Resize(s, INTER.CV_INTER_LINEAR).Convert<Gray, Byte>(), templateimage.Resize(s, INTER.CV_INTER_LINEAR).Convert<Gray, Byte>(), MatchColorSpaceType.Gray);
                //matchpoint = new Point((int)(p.X / s), (int)(p.Y / s));
            }

            intputiamge.Dispose();
            templateimage.Dispose();
            intputiamgeScale.Dispose();
            templateimageScale.Dispose();

            return md;
        }

        static TM_TYPE[] tmTypes = new TM_TYPE[] { TM_TYPE.CV_TM_SQDIFF_NORMED, TM_TYPE.CV_TM_CCORR, TM_TYPE.CV_TM_CCORR_NORMED, TM_TYPE.CV_TM_CCOEFF, TM_TYPE.CV_TM_CCOEFF_NORMED };
        //static TM_TYPE[] tmTypes = new TM_TYPE[] { TM_TYPE.CV_TM_SQDIFF, TM_TYPE.CV_TM_SQDIFF_NORMED, TM_TYPE.CV_TM_CCORR, TM_TYPE.CV_TM_CCORR_NORMED, TM_TYPE.CV_TM_CCOEFF, TM_TYPE.CV_TM_CCOEFF_NORMED };
        public static MatchData MatchAllResultHSV(this Bitmap intput, TemplateView template, double matchrate = 0.1)
        {
            var md = new MatchData() { TemplateView = template };

            var imageInput2 = (new Image<Hsv, Byte>((Bitmap)intput)).Resize(matchrate, INTER.CV_INTER_LINEAR);
            var imageTemplate2 = (new Image<Hsv, Byte>((Bitmap)template.Image)).Resize(matchrate, INTER.CV_INTER_LINEAR);

            Image<Gray, Single>[] imageResults = new Image<Gray, float>[tmTypes.Length];
            List<Point> points = new List<Point>();
            Point firstpoint = new Point();
            int i = 0;
            foreach (TM_TYPE tmType in tmTypes)
            {
                var imageResult = imageInput2.MatchTemplate(imageTemplate2, tmType);
                CvInvoke.cvNormalize(imageResult.Ptr, imageResult.Ptr, 1d, 0d, NORM_TYPE.CV_MINMAX, IntPtr.Zero);
                double bestValue;
                Point bestPoint;
                FindBestMatchPointAndValue(imageResult, tmType, out bestValue, out bestPoint);
                points.Add(bestPoint);
                imageResults[i] = imageResult;
                i++;

                firstpoint = bestPoint;
            }

            imageInput2.Dispose();
            imageTemplate2.Dispose();
            foreach (Image<Gray, Single> imageResult in imageResults)
            {
                imageResult.Dispose();
            }

            if (points.All(p => p.X == firstpoint.X && p.Y == firstpoint.Y))
            {
                md.MatchRate = 1;
                md.IsMatch = true;
                md.Point = new Point((int)(firstpoint.X / matchrate) + template.Point.X, (int)(firstpoint.Y / matchrate) + template.Point.Y);
            }
            return md;
        }

        public static MatchData MatchAllResultGray(this Bitmap intput, TemplateView template, double matchrate = 0.1)
        {
            var md = new MatchData() { TemplateView = template };

            var imageInput2 = (new Image<Gray, Byte>((Bitmap)intput)).Resize(matchrate, INTER.CV_INTER_LINEAR);
            var imageTemplate2 = (new Image<Gray, Byte>((Bitmap)template.Image)).Resize(matchrate, INTER.CV_INTER_LINEAR);

            Image<Gray, Single>[] imageResults = new Image<Gray, float>[tmTypes.Length];
            List<Point> points = new List<Point>();
            Point firstpoint = new Point();
            int i = 0;
            foreach (TM_TYPE tmType in tmTypes)
            {
                var imageResult = imageInput2.MatchTemplate(imageTemplate2, tmType);
                CvInvoke.cvNormalize(imageResult.Ptr, imageResult.Ptr, 1d, 0d, NORM_TYPE.CV_MINMAX, IntPtr.Zero);
                double bestValue;
                Point bestPoint;
                FindBestMatchPointAndValue(imageResult, tmType, out bestValue, out bestPoint);
                points.Add(bestPoint);
                imageResults[i] = imageResult;
                i++;

                firstpoint = bestPoint;
            }

            imageInput2.Dispose();
            imageTemplate2.Dispose();
            foreach (Image<Gray, Single> imageResult in imageResults)
            {
                imageResult.Dispose();
            }

            if (points.All(p => p.X == firstpoint.X && p.Y == firstpoint.Y))
            {
                md.MatchRate = 1;
                md.IsMatch = true;
                md.Point = new Point((int)(firstpoint.X / matchrate) + template.Point.X, (int)(firstpoint.Y / matchrate) + template.Point.Y);
            }
            return md;
        }

        public static MatchData MatchAllResultRGB(this Bitmap intput, TemplateView template, double matchrate = 0.1)
        {
            var md = new MatchData() { TemplateView = template };

            var imageInput2 = (new Image<Bgr, Byte>((Bitmap)intput)).Resize(matchrate, INTER.CV_INTER_LINEAR);
            var imageTemplate2 = (new Image<Bgr, Byte>((Bitmap)template.Image)).Resize(matchrate, INTER.CV_INTER_LINEAR);

            Image<Gray, Single>[] imageResults = new Image<Gray, float>[tmTypes.Length];
            List<Point> points = new List<Point>();
            Point firstpoint = new Point();
            int i = 0;
            foreach (TM_TYPE tmType in tmTypes)
            {
                var imageResult = imageInput2.MatchTemplate(imageTemplate2, tmType);
                CvInvoke.cvNormalize(imageResult.Ptr, imageResult.Ptr, 1d, 0d, NORM_TYPE.CV_MINMAX, IntPtr.Zero);
                double bestValue;
                Point bestPoint;
                FindBestMatchPointAndValue(imageResult, tmType, out bestValue, out bestPoint);
                points.Add(bestPoint);
                imageResults[i] = imageResult;
                i++;

                firstpoint = bestPoint;
            }

            imageInput2.Dispose();
            imageTemplate2.Dispose();
            foreach (Image<Gray, Single> imageResult in imageResults)
            {
                imageResult.Dispose();
            }

            if (points.All(p => p.X == firstpoint.X && p.Y == firstpoint.Y))
            {
                md.MatchRate = 1;
                md.IsMatch = true;
                md.Point = new Point((int)(firstpoint.X / matchrate) + template.Point.X, (int)(firstpoint.Y / matchrate) + template.Point.Y);
            }
            return md;
        }

        //找到最匹配的点，以及该点的值
        private static void FindBestMatchPointAndValue(Image<Gray, Single> image, TM_TYPE tmType, out double bestValue, out Point bestPoint)
        {
            bestValue = 0d;
            bestPoint = new Point(0, 0);
            double[] minValues, maxValues;
            Point[] minLocations, maxLocations;
            image.MinMax(out minValues, out maxValues, out minLocations, out maxLocations);
            //对于平方差匹配和归一化平方差匹配，最小值表示最好的匹配；其他情况下，最大值表示最好的匹配
            if (tmType == TM_TYPE.CV_TM_SQDIFF || tmType == TM_TYPE.CV_TM_SQDIFF_NORMED)
            {
                bestValue = minValues[0];
                bestPoint = minLocations[0];
            }
            else
            {
                bestValue = maxValues[0];
                bestPoint = maxLocations[0];
            }
        }

        private static Point GetBestPoint(IImage imageInput2, IImage imageTemplate2, out double matchrate, MatchColorSpaceType colorSpace = MatchColorSpaceType.RGB)
        {
            Image<Gray, Single> imageResult;
            if (colorSpace == MatchColorSpaceType.Gray)
            {
                imageResult = ((Image<Gray, Byte>)imageInput2).MatchTemplate((Image<Gray, Byte>)imageTemplate2, TM_TYPE.CV_TM_SQDIFF_NORMED);
            }
            else if (colorSpace == MatchColorSpaceType.HSV)
            {
                imageResult = ((Image<Hsv, Byte>)imageInput2).MatchTemplate((Image<Hsv, Byte>)imageTemplate2, TM_TYPE.CV_TM_SQDIFF_NORMED);
            }
            else
            {
                imageResult = ((Image<Bgr, Byte>)imageInput2).MatchTemplate((Image<Bgr, Byte>)imageTemplate2, TM_TYPE.CV_TM_SQDIFF_NORMED);
            }

            CvInvoke.cvNormalize(imageResult.Ptr, imageResult.Ptr, 1d, 0d, NORM_TYPE.CV_MINMAX, IntPtr.Zero);

            Point bestPoint;
            FindBestMatchPointAndValue(imageResult, TM_TYPE.CV_TM_SQDIFF_NORMED, out matchrate, out bestPoint);

            imageResult.Dispose();

            return bestPoint;
        }

        private static bool IsIngornSpanSame(this Point first, Point second)
        {
            return Math.Abs(first.X - second.X) <= ErrorSapn && Math.Abs(first.Y - second.Y) <= ErrorSapn;
        }

    }
}
